package cn.xawl.gulimall.search.service.impl;

import cn.xawl.common.to.es.SkuEsModel;
import cn.xawl.common.utils.R;
import cn.xawl.gulimall.search.config.ESConfig;
import cn.xawl.gulimall.search.constant.EsIndexConstant;
import cn.xawl.gulimall.search.feign.ProductFeignService;
import cn.xawl.gulimall.search.service.SearchService;
import cn.xawl.gulimall.search.vo.AttrResponseVo;
import cn.xawl.gulimall.search.vo.SearchParams;
import cn.xawl.gulimall.search.vo.SearchResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lh
 * @create 2020-10-25 13:01
 **/
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient esClient;

    @Autowired
    private ProductFeignService productFeign;

    @Override
    public SearchResult search(SearchParams params) {


        SearchRequest searchRequest = buildSearchProReq(params);

        SearchResult result = null;
        try {
            SearchResponse response = esClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
            result = buildSearchProResult(response, params);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }



    /**
     * 根据前台的条件参数构建一个SearchRequest对象
     * @return {@link SearchRequest}
     */
    private SearchRequest buildSearchProReq(SearchParams params) {
        //开始构建一个SearchRequest
        SearchRequest searchRequest = new SearchRequest();
        //指定查询哪个索引
        searchRequest.indices(EsIndexConstant.PRODUCT_INDEX);

        //开始构建SearchSourceBuilder
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //获取bool查询
        BoolQueryBuilder query = QueryBuilders.boolQuery();


        /*
        #一次完整的查询需要包含:
        #1. 模糊匹配
        #2. 属性、分类品牌、库存、价格区间等属性过滤
        #3. 排序
        #4. 分页
        #5. 高亮
        #6. 聚合分析
        * */
        //如果关键字存在，就构建一个模糊匹配查询
        if (!StringUtils.isEmpty(params.getKeyword())) {
            query.must(QueryBuilders.matchQuery("skuTitle", params.getKeyword()));
        }

        //品牌id:termsQuery
        List<QueryBuilder> filter = query.filter();
        if (!CollectionUtils.isEmpty(params.getBrandId())) {
            filter.add(QueryBuilders.termsQuery("brandId", params.getBrandId()));
        }
        
        //分类id：termQuery
        if (params.getCatalog3Id() != null) {
            filter.add(QueryBuilders.termQuery("catalogId", params.getCatalog3Id()));
        }
        
        //嵌套类型的属性查询
        List<String> attrs = params.getAttrs();
        if (!CollectionUtils.isEmpty(attrs)) {
            for (String attr : attrs) {
                if (!StringUtils.isEmpty(attr)) {
                    //每一个attr：1_IOS:安卓:其他
                    String[] attrInfo = attr.split("_");

                    BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                    nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrInfo[0]));
                    nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrInfo[1].split(":")));

                    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                    filter.add(nestedQuery);
                }
            }
        }

        //hasStock 是否有库存的条件（默认有货）
        filter.add(QueryBuilders.termQuery("hasStock", params.getHasStock() == 1));

        String skuPrice = params.getSkuPrice();
        //skuPrice（价格区间）：1_200【1-200元】/_200【小于200元】/200_【大于200元】
        if (!StringUtils.isEmpty(skuPrice)) {
            String[] priceRange = skuPrice.split("_"); //获取价格区间
            if (priceRange.length == 2) {
                //如果数组长度为2，说明既有大于条件，又有小于条件
                filter.add(QueryBuilders.rangeQuery("skuPrice").gte(Integer.valueOf(priceRange[0])).lte(Integer.valueOf(priceRange[1])));
            } else if (priceRange.length == 1) {
                //如果数组长度为1，说明只有大于或者等于的条件
                String price = priceRange[0];
                if (skuPrice.startsWith("_")) {
                    //价格区间字符串以_开始，则只有小于的条件
                    if (!StringUtils.isEmpty(price)) {

                        filter.add(QueryBuilders.rangeQuery("skuPrice").lte(Integer.valueOf(price)));
                    }
                } else if (skuPrice.endsWith("_")) {
                    //价格区间字符串以_结束，则只有大于的条件
                    if (!StringUtils.isEmpty(price)) {

                        filter.add(QueryBuilders.rangeQuery("skuPrice").gte(Integer.valueOf(price)));
                    }
                }
            }
        }


        sourceBuilder.query(query);

        //sort属性  skuPrice_asc/desc
        String sort = params.getSort();
        if (!StringUtils.isEmpty(sort)) {
            String[] sortStr = sort.split("_");
            if (sortStr.length == 2) {
                if (sortStr[1].equalsIgnoreCase("asc") || sortStr[1].equalsIgnoreCase("desc")) {
                    SortOrder sortOrder =  sortStr[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
                    sourceBuilder.sort(sortStr[0], sortOrder);
                }
            }
        }


        //分页
        if (params.getPageNum() != null) {
            sourceBuilder.size(EsIndexConstant.PRODUCT_PAGE_SIZE);
            sourceBuilder.from((params.getPageNum() - 1) * EsIndexConstant.PRODUCT_PAGE_SIZE);
        }
        sourceBuilder.size(EsIndexConstant.PRODUCT_PAGE_SIZE);
        sourceBuilder.from(0);




        //高亮
        if (!StringUtils.isEmpty(params.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }


        //构建一个品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg");
        brandAgg.field("brandId").size(50);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);

        //构建分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg");
        catalogAgg.field("catalogId").size(20);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalogAgg);

        //构建属性聚合
        NestedAggregationBuilder attrNested = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
        attrNested.subAggregation(attrIdAgg);
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        sourceBuilder.aggregation(attrNested);

        searchRequest.source(sourceBuilder);

        System.out.println(sourceBuilder);

        return searchRequest;
    }

    private SearchResult buildSearchProResult(SearchResponse response, SearchParams params) {
        SearchResult result = new SearchResult();


        Aggregations aggregations = response.getAggregations();

        if (aggregations != null) {
            if (!CollectionUtils.isEmpty(aggregations.asList())) {
                //获取分类id聚合
                ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
                if (catalogAgg != null) {
                    List<? extends Terms.Bucket> catalogAggBuckets = catalogAgg.getBuckets();
                    List<SearchResult.CatalogVo> catalogs = new ArrayList<>();
                    for (Terms.Bucket catalogAggBucket : catalogAggBuckets) {
                        SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
                        String catalogId = catalogAggBucket.getKeyAsString();
                        catalogVo.setCatalogId(Long.valueOf(catalogId));

                        ParsedStringTerms catalogNameAgg = catalogAggBucket.getAggregations().get("catalog_name_agg");
                        //因为每个分类di只对应一个分类名字，因此只需要获取第一个bucket中的信息即可
                        String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
                        catalogVo.setCatalogName(catalogName);
                        catalogs.add(catalogVo);
                    }
                    result.setCatalogs(catalogs);
                }

                //获取品牌聚合结果
                ParsedLongTerms brandAgg = aggregations.get("brand_agg");
                if (brandAgg != null) {
                    List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
                    List<SearchResult.BrandVo> brands = new ArrayList<>();
                    for (Terms.Bucket bucket : buckets) {
                        SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                        brandVo.setBrandId(Long.valueOf(bucket.getKeyAsString()));

                        //品牌聚合的子聚合
                        //获取品牌图片聚合
                        ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
                        String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                        brandVo.setBrandImg(brandImg);

                        //获取品牌名字聚合
                        ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
                        String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                        brandVo.setBrandName(brandName);

                        brands.add(brandVo);
                    }
                    //品牌
                    result.setBrands(brands);
                }

                ParsedNested attrAgg = aggregations.get("attr_agg");
                if (attrAgg != null) {
                    List<SearchResult.AttrVo> attrs = new ArrayList<>();

                    ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");

                    List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();
                    for (Terms.Bucket attrIdBucket : buckets) {
                        SearchResult.AttrVo attrVo = new SearchResult.AttrVo();

                        String attrId = attrIdBucket.getKeyAsString();
                        attrVo.setAttrId(Long.valueOf(attrId));

                        ParsedStringTerms attrNameAgg = attrIdBucket.getAggregations().get("attr_name_agg");
                        String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                        attrVo.setAttrName(attrName);

                        ParsedStringTerms attrValueAgg = attrIdBucket.getAggregations().get("attr_value_agg");
                        List<? extends Terms.Bucket> attrValueBuckets = attrValueAgg.getBuckets();
                        List<String> attrValues = new ArrayList<>();
                        for (Terms.Bucket attrValueBucket : attrValueBuckets) {
                            String attrValue = attrValueBucket.getKeyAsString();
                            attrValues.add(attrValue);
                        }
                        attrVo.setAttrValue(attrValues);

                        attrs.add(attrVo);
                    }
                    //设置属性
                    result.setAttrs(attrs);
                }


            }

        }



        //=======以上结果都从聚合查询中取出======

        SearchHits hits = response.getHits();
        if (hits != null) {

            //当前页码 如果没有传递任何页码，则默认为第一页
            Integer pageNum = params.getPageNum();
            if (pageNum != null) {
                result.setPageNum(pageNum);
            } else {
                result.setPageNum(1);
            }

            //总页码
            long total = hits.getTotalHits().value;
            result.setTotalPages((int)total % EsIndexConstant.PRODUCT_PAGE_SIZE == 0 ? (int)total / EsIndexConstant.PRODUCT_PAGE_SIZE : ((int)total / EsIndexConstant.PRODUCT_PAGE_SIZE + 1));

            //products
            List<SkuEsModel> skuEsModels = new ArrayList<>();
            for (SearchHit hit : hits) {
                //获取 命中的记录的json字符串
                String esModelJson = hit.getSourceAsString();
                //将json字符串转换为目标对象
                SkuEsModel skuEsModel = JSON.parseObject(esModelJson, SkuEsModel.class);

                //判断是否有高亮显示
                HighlightField highlightField = hit.getHighlightFields().get("skuTitle");
                if (highlightField != null) {
                    String skuTitle = highlightField.getFragments()[0].string();
                    skuEsModel.setSkuTitle(skuTitle);
                }

                skuEsModels.add(skuEsModel);
            }
            result.setProducts(skuEsModels);

            result.setTotal(total);

            List<Integer> pageNavs = new ArrayList<>();
            for (int i = 1; i <= result.getTotalPages(); i++) {
                pageNavs.add(i);
            }
            result.setPageNavs(pageNavs);
        }

        List<String> attrs = params.getAttrs();
        if (!CollectionUtils.isEmpty(attrs)) {

            List<SearchResult.NavVo> navVoList = attrs.stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] str = attr.split("_");
                navVo.setNavValue(str[1]);
                R info = productFeign.info(Long.parseLong(str[0]));
                if ((int)info.get("code") == 0) {
                    AttrResponseVo attrInfo = info.getData(new TypeReference<AttrResponseVo>() {}, "attr");
                    navVo.setNavName(attrInfo.getAttrName());
                } else {
                    navVo.setNavName("");
                }
                String queryString = params.get_queryString();
                if (!StringUtils.isEmpty(queryString)) {
                    String encode = null;
                    try {
                        encode = URLEncoder.encode(attr, "UTF-8");
                        encode = encode.replace("+", "%20");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }

                    String url = params.get_url();
                    int index = url.indexOf("?");
                    if (url.startsWith("attrs", index + 1)) {
                        String replace = queryString.replace("attrs=" + encode, "");
                        navVo.setLink("http://search.gulimall.com/search.html?" + replace);
                        return navVo;
                    }

                    String replace = queryString.replace("&attrs=" + encode, "");
                    String replaceStr = "attrs=" + encode;
                    if (!replace.equals(replaceStr)) {
                        navVo.setLink("http://search.gulimall.com/search.html?" + replace);
                    } else {
                        navVo.setLink("http://search.gulimall.com/search.html");
                    }
                }
                result.getAttrIds().add(Long.parseLong(str[0]));
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(navVoList);
        }




        return result;
    }


}
